/*-------------------------------------------------------------------------*\
**  STON - Strongly Typed Object Notation                                  **
**  Copyright (c) 2009, Erkki Lindpere. All rights reserved.               **
**  http://code.google.com/p/ston/                                         **
**                                                                         **
**  This software is released under the terms of the Revised BSD License.  **
**  There is NO WARRANTY. See the file LICENSE for the full text.          **
\*-------------------------------------------------------------------------*/
package org.villane.ston

import scala.util.parsing.syntax._
import scala.util.parsing.combinator.syntactical._

/**
 * STON - Strongly Typed Object Notation
 * 
 * JSON-like syntax for defining strongly typed objects
 * 
 * Not meant to be used as is -- extensions should define the actual types.
 */
class STONParser extends StdTokenParsers {
  type Tokens = StdTokens
  val lexical = new STONLexer
  lexical.delimiters += (
    "(", ")",
    "{", "}",
    "[", "]",
    ":",
    ",",
    "@"
  )
  lexical.reserved += (
    "true", "false"
  )
  def namedValues = _namedValues
  var _namedValues = collection.mutable.Map[String,Any]() 

  def namedValue[T](valueParser: => Parser[T]): Parser[T] = opt(ident <~ "@") ~ valueParser ^^ {
    case Some(name) ~ v =>
      if (_namedValues.contains(name)) {
        error("Named value '" + name + "' is defined twice!")
      } else {
        _namedValues += name -> v
        v
      }
    case None ~ v => v
    //case v => v
  }
  
  sealed abstract class Type[+T](clazz: Class[T]) {
    def defaultValue: T
    def parser: Parser[T]
  }

  abstract class SimpleType[+T](clazz: Class[T]) extends Type(clazz)

  case class ReferenceType[T](clazz: Class[T]) extends SimpleType(clazz) {
    // could return null, but that would limit References to AnyRef/Null types.
    def defaultValue = throw new UnsupportedOperationException()
    def parser = namedValue(ident ^^ {
      case n if (_namedValues.contains(n) && clazz.isAssignableFrom((_namedValues(n).asInstanceOf[AnyRef]).getClass)) => _namedValues(n).asInstanceOf[T]
      case n => error("Invalid reference '" + n + "'")
    })
  }

  case class ArrayType[T](typ: Type[T]) extends Type(classOf[Array[T]]) {
    def defaultValue = new Array[T](0)
    def parser = namedValue("[" ~> repsep(typ.parser, ","?) <~ "]" ^^ { case l => l.toArray })
  }

  case class AbstractDataType[T](baseClazz: Class[T],
                                 types: Array[Type[T]]) extends Type(baseClazz) {
    def defaultValue = throw new UnsupportedOperationException
    def parser = types map (_.parser) reduceRight (_|_)
  }

  case class Property[T](name: String, typ: Type[T]) {
    def defaultValue = typ.defaultValue
  }
  case class PropertyWithDefault[T](nme: String, tpe: Type[T], override val defaultValue: T) extends Property(nme, tpe)
  case class PropertyValue[T](name: String, value: T)

  abstract class ComplexType[+T](clazz: Class[T],
                                 val properties: Array[Property[_]]) extends Type(clazz) {
    def create(values: List[PropertyValue[_]]): T = { 
      var map = Map[String,Any]()
      for (PropertyValue(name, value) <- values) {
        if (map.contains(name))
          // TODO return parse failure
          error("Property '" + name + "' defined twice!")
        else
            map += name -> value
      }
      for (p <- properties) {
        if (! map.contains(p.name)) {
          map += p.name -> p.defaultValue
        }
      }
      create(map)
    }
    def create(map: collection.Map[String,Any]): T
    def name = clazz.getSimpleName
    def parser = namedValue((lexical.Identifier(name) ~ "{" ~> repsep(property, ","?) <~ "}") ^^ {
      case props => create(props)
	})
    def property = properties.map(singleProperty(_)).reduceRight {
      (a:Parser[PropertyValue[_]],b:Parser[PropertyValue[_]]) => a|b
    }
    def singleProperty(prop: Property[_]) = lexical.Identifier(prop.name) ~ ":" ~ prop.typ.parser ^^ {
      case lexical.Identifier(name) ~ ":" ~ value => PropertyValue(name, value)
    }
  }

  def singleProperty[T](name: String, typ: Type[T]) = lexical.Identifier(name) ~ ":" ~ typ.parser ^^ {
    case lexical.Identifier(`name`) ~ ":" ~ value => value
  }
}
